﻿using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using ToolGetData.Data_Access_Layer;
using ToolGetData.Entities;
using ToolGetData.Enums;
using ToolGetData.NotificationService;

namespace ToolGetData
{
    public partial class frmGetData : Form
    {
        string baseAddress = "http://ringtones.mob.org";
        WebClient webClient;
        RingtoneDAL ringtoneDAL;
        NotificationClient notificationClient;
        Thread thread;
        Category currentCategory;
        int count = 0;
        List<int> totalPagesList;//danh sách chứa tổng số page chứa nhạc chuông đã lấy dữ liệu của các thể loại 
        List<string> categoryList;


        private delegate int MethodInvoker_GetSelectedIndex(ComboBox comboBox);
        private delegate void MethodInvoker_SetButtonText(Button button, string text);
        private delegate void MethodInvoker_SetLabelText(Label button, string text);
        private delegate DialogResult MethodInvoker_ShowMessage(string text);
        private delegate void MethodInvoker_SetProgress(ProgressBar progressBar, int x);
        private delegate void MethodInvoker_PerformStep(ProgressBar progressBar);
        private delegate int MethodInvoker_GetProgressBar(ProgressBar progressBar);
        private delegate void MethodInvoker_UpdatePercentage(Label label, double percentage);
        private delegate void MethodInvoker_UpdateCurrentPage(Label label, int currentPage);
        private delegate void MethodInvoker_SetButtonEnabled(Button button, bool enabled);
        private delegate string MethodInvoker_GetText(ComboBox comboBox);

        public frmGetData()
        {
            InitializeComponent();
            webClient = new WebClient();
            ringtoneDAL = new RingtoneDAL();
            notificationClient = new NotificationClient();
            this.categoryList = new List<string>();
            this.categoryList.AddRange(Enum.GetNames(typeof(Category)));
            this.totalPagesList = GetTotalPagesFromFile();
        }

        #region Events

        private void btnStart_Click(object sender, EventArgs e)
        {
            btnStart.Enabled = false;
            thread = new Thread(GetData);
            thread.Start();
        }

        #endregion


        #region Thread-safe
        private void UpdatePercentage(Label label, double percentage)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker_UpdatePercentage(UpdatePercentage), new object[] { label, percentage });
            }
            else
            {
                label.Text = percentage + " %";
            }
        }

        private void UpdateCurrentPage(Label label, int currentPage)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker_UpdateCurrentPage(UpdateCurrentPage), new object[] { label, currentPage });
            }
            else
            {
                label.Text = currentPage.ToString();
            }
        }

        private void SetButtonText(Button button, string text)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker_SetButtonText(SetButtonText), new object[] { button, text });
            }
            else
            {
                button.Text = text;
            }
        }

        private void SetLabelText(Label label, string text)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker_SetLabelText(SetLabelText), new object[] { label, text });
            }
            else
            {
                label.Text = text;
            }
        }

        private void SetButtonEnabled(Button button, bool enabled)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker_SetButtonEnabled(SetButtonEnabled), new object[] { button, enabled });
            }
            else
            {
                button.Enabled = enabled;
            }
        }

        private void SetProgressMax(ProgressBar progressBar, int max)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker_SetProgress(SetProgressMax), new object[] { progressBar, max });
            }
            else
            {
                progressBar.Maximum = max;
            }
        }

        private void SetProgressValue(ProgressBar progressBar, int value)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker_SetProgress(SetProgressValue), new object[] { progressBar, value });
            }
            else
            {
                progressBar.Value = value;
            }
        }

        private int GetProgressMax(ProgressBar progressBar)
        {
            if (this.InvokeRequired)
            {
                return (int)this.Invoke(new MethodInvoker_GetProgressBar(GetProgressMax), new object[] { progressBar });
            }
            else
            {
                return progressBar.Maximum;
            }
        }

        private int GetProgressValue(ProgressBar progressBar)
        {
            if (this.InvokeRequired)
            {
                return (int)this.Invoke(new MethodInvoker_GetProgressBar(GetProgressValue), new object[] { progressBar });
            }
            else
            {
                return progressBar.Value;
            }
        }

        private void PerformStep(ProgressBar progressBar)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker_PerformStep(PerformStep), new object[] { progressBar });
            }
            else
            {
                progressBar.PerformStep();
            }
        }

        private DialogResult ShowMessage(string text)
        {
            if (this.InvokeRequired)
            {
                return (DialogResult)this.Invoke(new MethodInvoker_ShowMessage(ShowMessage), new object[] { text });
            }
            else
            {
                return MessageBox.Show(text);
            }
        }

        private int GetSelectedIndex(ComboBox comboBox)
        {
            if (this.InvokeRequired)
            {
                return (int)this.Invoke(new MethodInvoker_GetSelectedIndex(GetSelectedIndex), new object[] { comboBox });
            }
            else
            {
                return comboBox.SelectedIndex;
            }
        }

        private string GetText(ComboBox comboBox)
        {
            if (this.InvokeRequired)
            {
                return this.Invoke(new MethodInvoker_GetText(GetText), new object[] { comboBox }).ToString();
            }
            else
            {
                return comboBox.Text;
            }
        }
        #endregion

        private void GetData()
        {
            while (true)
            {
                string cateUrl;
                for (int i = 0; i < this.categoryList.Count; i++)
                {
                    this.currentCategory = (Category)i;
                    SetLabelText(lblCategory, this.currentCategory.ToString());
                    cateUrl = GetCateUrl(this.currentCategory);
                    UpdateRingtone(i + 1, cateUrl);
                }
            }
        }

        /// <summary>
        /// Quét các trang nhạc chuông thuộc thể loại có Id là cateId, mã là cateCode rồi lưu các nhạc chuông get được vào database
        /// </summary>
        /// <param name="cateId">Id của thể loại nhạc chuông trong database</param>
        /// <param name="cateUrl">Mã thể loại</param>
        private void UpdateRingtone(int cateId, string cateUrl)
        {
            bool bStop = false;// = true có nghĩa là một trang nhạc chuông trên imuzik.com.vn bị lỗi
            bool bNew = false;//  = true có nghĩa là trang imuzik.com.vn update trang nhạc chuông mới
            int downPages = 0;// số trang đã download
            string cateAddress = baseAddress + cateUrl;// địa chỉ của trang nhạc chuông có mã thể loại (category code) là cateCode
            int cateIndex = cateId - 1;// chỉ số category của mảng this.totalPagesArray tính từ 0
            int newTotalPages = GetTotalPagesFromUrl(new Uri(cateAddress, UriKind.Absolute)); //Tổng số trang nhạc chuông trên trang cateAddress
            if (newTotalPages > 0)
            {
                int newPages = newTotalPages - this.totalPagesList[cateIndex] + 1;// số trang nhạc chuông mà imuzik.com.vn mới cập nhật thêm
                SetLabelText(lblTotalPages, newPages.ToString());
                SetProgressMax(progressBar, newPages);
                //Quét từ trang số newPages về trang số 1
                for (int i = newPages; i >= 1; i--)
                {
                    UpdateCurrentPage(lblCurrentPage, i);//Update chỉ số trang hiện tại đang lấy dữ liệu lên lblCurrentPaage
                    string fulladdress = baseAddress + cateUrl + "page_" + i + "/";//địa chỉ trang thứ i thuộc thể loại có mà là cateCode
                    Stack<Ringtone> ringtoneStack = GetAllRingtonesFromUrl(new Uri(fulladdress, UriKind.Absolute), cateId);

                    //Nếu số nhạc chuông get được từ địa chỉ fulladdress > 0
                    if (ringtoneStack.Count > 0)
                    {
                        bStop = false;//có nghĩa là trang fulladdress không bị lỗi
                        this.count += ringtoneDAL.InsertRingtones(ringtoneStack);//Insert các ringtone get được và database
                        downPages++;

                        PerformStep(progressBar);//tăng % hoàn thành trên progressBar
                        double value = GetProgressValue(progressBar);
                        double maximum = GetProgressMax(progressBar);
                        double percentage = Convert.ToDouble(value) / Convert.ToDouble(maximum) * 100;//% hoàn thành
                        percentage = Math.Round(percentage, 2);
                        UpdatePercentage(lblPercentage, percentage);//Update % hoàn thành trên lblPercentage

                        //Nếu đã quét xong các trang nhạc chuông mới thuộc thể loại có mã là cateCode thì
                        if (GetProgressValue(progressBar) == GetProgressMax(progressBar))
                        {
                            if (this.count > 0)
                            {
                                notificationClient.SendToAllClient(this.currentCategory.ToString(), this.count);
                                this.count = 0;
                                this.totalPagesList[cateIndex] = newTotalPages;
                                SaveTotalPages();
                            }
                            SetProgressValue(progressBar, 0);
                            UpdatePercentage(lblPercentage, 0);
                            break;

                            #region Kiểm tra trang web có cập nhật nhạc chuông hay ko
                            int newTotalPages1 = GetTotalPagesFromUrl(new Uri(cateAddress, UriKind.Absolute));
                            //nếu imuzik.com update nhạc chuông mới thì
                            if (newTotalPages1 > newTotalPages)
                            {
                                bNew = true;//có nghĩa là trang imuzik update trang nhạc chuông mới 
                                SetProgressValue(progressBar, 0);
                                UpdatePercentage(lblPercentage, 0);
                                break;
                            }
                            else
                            {
                                bNew = false;
                            }
                            #endregion
                        }
                        else
                        {
                            #region Kiểm tra trang web có cập nhật nhạc chuông hay không
                            int newTotalPages1 = GetTotalPagesFromUrl(new Uri(cateAddress, UriKind.Absolute));
                            //nếu imuzik.com update nhạc chuông mới thì
                            if (newTotalPages1 > newTotalPages)
                            {
                                bNew = true;//có nghĩa là trang imuzik update trang nhạc chuông mới 
                                this.totalPagesList[cateIndex] += downPages;
                                SaveTotalPages();
                                SetProgressValue(progressBar, 0);
                                UpdatePercentage(lblPercentage, 0);
                                break;
                            }
                            else
                            {
                                bNew = false;
                            }
                            #endregion
                        }

                    }
                    //Nếu số nhạc chuông get được từ địa chỉ fulladdress = 0
                    else
                    {
                        bStop = true;//có nghĩa là trang fulladdress bị lỗi
                        this.totalPagesList[cateIndex] += downPages;
                        SaveTotalPages();
                        SetProgressValue(progressBar, 0);
                        UpdatePercentage(lblPercentage, 0);
                        break;
                    }

                }
            }
            else
            {
                bStop = true;//có nghĩa là trang fulladdress bị lỗi
                this.totalPagesList[cateIndex] += downPages;
                SaveTotalPages();
                SetProgressValue(progressBar, 0);
                UpdatePercentage(lblPercentage, 0);
            }
            //Nếu có trang bị lỗi hoặc imizik update trang nhạc chuông thuộc thể loại cateId, cateCode thì quét lại
            if (bStop || bNew)
            {
                UpdateRingtone(cateId, cateUrl);
            }

        }

        /// <summary>
        /// Get mã thể loại
        /// </summary>
        /// <param name="category"></param>
        /// <returns></returns>
        private string GetCateUrl(Category category)
        {
            string cateUrl; ;
            switch (category)
            {
                case Category.Dance:
                    cateUrl = "/tag=dance/";
                    break;
                case Category.Funnny:
                    cateUrl = "/tag=funny/";
                    break;
                case Category.HardHeavy:
                    cateUrl = "/tag=hard_heavy/";
                    break;
                case Category.HipHop:
                    cateUrl = "/tag=hip_hop/";
                    break;
                case Category.Holiday:
                    cateUrl = "/tag=holiday/";
                    break;
                case Category.HymnsMarches:
                    cateUrl = "/tag=hymns_and_marches/";
                    break;
                case Category.JazzBlues:
                    cateUrl = "/tag=jazz_and_blues/";
                    break;
                case Category.MoviesGames:
                    cateUrl = "/tag=movies_games/";
                    break;
                case Category.PopRock:
                    cateUrl = "/tag=pop_rock/";
                    break;
                case Category.RelaxingMusic:
                    cateUrl = "/tag=relaxing_music/";
                    break;
                case Category.SoulMusic:
                    cateUrl = "/tag=soul_music/";
                    break;
                case Category.Sounds:
                    cateUrl = "/tag=sounds/";
                    break;
                default:
                    cateUrl = "";
                    break;
            }
            return cateUrl;
        }

        /// <summary>
        /// Get toàn bộ nhạc chuông từ url thuộc thể loại có Id là cateId
        /// </summary>
        /// <param name="url"></param>
        /// <param name="cateId"></param>
        /// <returns></returns>
        private Stack<Ringtone> GetAllRingtonesFromUrl(Uri url, int cateId)
        {
            Stack<Ringtone> ringtoneStack = new Stack<Ringtone>();
            HtmlAgilityPack.HtmlDocument document = DownloadHtml(url);

            if (document != null)
            {
                HtmlNodeCollection nodeCollection = document.DocumentNode.SelectNodes("//div[@class='melodies_list']/a[@class='hd']");
                if (nodeCollection != null)
                {
                    foreach (HtmlNode node in nodeCollection)
                    {
                        string link = node.Attributes["href"].Value;
                        document = DownloadHtml(new Uri(link, UriKind.Absolute));
                        HtmlNode ringtoneNode = document.DocumentNode.SelectSingleNode("//div[@class='melody_full']");
                        if (ringtoneNode != null)
                        {
                            Ringtone ringtone = new Ringtone();
                            ringtone.Code = GetRingtoneCode(ringtoneNode);
                            ringtone.CateId = cateId;
                            ringtone.Url = new Uri(this.baseAddress + "/download/" + ringtone.Code + "/");
                            ringtone.Title = GetRingtoneTitle(ringtoneNode);
                            ringtone.Artist = GetRingtoneArtist(ringtoneNode);
                            ringtone.Download = 0;
                            ringtone.Size = GetSize(ringtoneNode);
                            ringtoneStack.Push(ringtone);
                        }
                        else
                        {
                            break;
                        }
                    }
                } 
            }
            return ringtoneStack;
        }


        private Uri GetMp3Link(HtmlNode ringtoneNode)
        {
            HtmlNode node = ringtoneNode.SelectSingleNode("./meta[@itemprop='contentURL']");
            string mp3Link = node.Attributes["content"].Value;
            return new Uri(mp3Link, UriKind.Absolute);
        }

        private string GetRingtoneTitle(HtmlNode ringtoneNode)
        {
            HtmlNode node = ringtoneNode.SelectSingleNode("(//a[@class='artist'])[last()]");
            string title = node.InnerText.Trim();
            title = title.Replace("'", "''");
            return title;
        }

        private string GetRingtoneArtist(HtmlNode ringtoneNode)
        {
            HtmlNode node = ringtoneNode.SelectSingleNode("(//a[@class='artist'])[1]");
            string artist = node.InnerText.Trim();
            artist = artist.Replace("'", "''");
            return artist;
        }

        private long GetRingtoneCode(HtmlNode ringtoneNode)
        {
            HtmlNode node = ringtoneNode.SelectSingleNode("//div[@class='code']");
            string str = node.InnerText;
            long code = ExtractNumbers(str);
            return code;
        }

        private int GetSize(HtmlNode ringtoneNode)
        {
            HtmlNode node = ringtoneNode.SelectSingleNode("//div[@style='float: left; font: 13px Arial; color: #909090; margin-left: 20px; margin-top: 7px;']");
            string str = node.InnerText;
            int size = (int) ExtractNumbers(str);
            return size;
        }

        private long ExtractNumbers(string str)
        {
            string snum = Regex.Match(str, @"\d+").Value;
            long num;
            if (!long.TryParse(snum, out num))
                throw new Exception();
            return num;
        }

        private int GetTotalPagesFromUrl(Uri url)
        {
            HtmlAgilityPack.HtmlDocument document = DownloadHtml(url);
            if (document != null)
            {
                HtmlNode node = document.DocumentNode.SelectSingleNode("//div[@class='stride_list']/select/option[last()]");
                string str = node.Attributes["value"].Value;
                int totalPages = (int)ExtractNumbers(str);
                return totalPages;
            }
            else
            {
                return 0;
            }
            
        }

        private List<int> GetTotalPagesFromFile()
        {
            List<int> totalPagesList = new List<int>();
            try
            {
                using (StreamReader reader = File.OpenText(@"totalpages.txt"))
                {
                    for (int i = 0; i < this.categoryList.Count; i++)
                    {
                        string line = reader.ReadLine();
                        int index = line.IndexOf("=");
                        int totalpages = int.Parse(line.Substring(index + 1));
                        totalPagesList.Add(totalpages);
                    }
                }
            }
            catch (FileNotFoundException)
            {
            }
            return totalPagesList;
        }

        private void SaveTotalPages()
        {
            using (StreamWriter writer = File.CreateText(@"\totalpages.txt"))
            {
                for (int i = 0; i < this.categoryList.Count; i++ )
                {
                    writer.WriteLine(this.categoryList[i] + "=" + this.totalPagesList[i]);
                }
            }
        }

        private HtmlAgilityPack.HtmlDocument DownloadHtml(Uri url)
        {
            try
            {
                string html = webClient.DownloadString(url);
                HtmlAgilityPack.HtmlDocument document = new HtmlAgilityPack.HtmlDocument();
                document.LoadHtml(html);
                return document;
            }
            catch (Exception)
            {
                return null;
            }
        }

    }
}
